package wang.naifei.core.utils.image;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * <dt>Description:ImageScale</dt>
 * <dd>图片压缩</dd>
 * <dd>author: wnfbx@163.com</dd>
 * <dd>version:1.0 <i>2022/1/21 23:23</i></dd>
 *
 * @since jdk1.7+
 */
public class ImageScale {

    private static final Logger log = LoggerFactory.getLogger(ImageScale.class);

    private int width;
    private int height;
    private int scaleWidth;
    private double[] contrib;
    private double[] normContrib;
    private double[] tmpContrib;
    private int nDots;
    private int nHalfDots;

    public BufferedImage imageZoomOut(BufferedImage srcBufferImage, int w, int h, boolean lockScale) {
        width = srcBufferImage.getWidth();
        height = srcBufferImage.getHeight();
        scaleWidth = w;
        if (lockScale) {
            h = w * height / width;
        }

        if (DetermineResultSize(w, h) == 1) {
            return srcBufferImage;
        }
        CalContrib();
        BufferedImage pbOut = HorizontalFiltering(srcBufferImage, w);
        return VerticalFiltering(pbOut, h);
    }

    /**
     * 决定图像尺寸
     */
    private int DetermineResultSize(int w, int h) {
        double scaleH, scaleV;
        scaleH = (double) w / (double) width;
        scaleV = (double) h / (double) height;
        // 需要判断一下scaleH，scaleV，不做放大操作
        if (scaleH >= 1.0 && scaleV >= 1.0) {
            return 1;
        }
        return 0;

    } // end of DetermineResultSize()

    private double Lanczos(int i, int inWidth, int outWidth, double Support) {
        double x;

        x = (double) i * (double) outWidth / (double) inWidth;

        return Math.sin(x * Math.PI) / (x * Math.PI) * Math.sin(x * Math.PI / Support) / (x * Math.PI / Support);

    } // end of Lanczos()

    //
    // Assumption: same horizontal and vertical scaling factor
    //
    private void CalContrib() {
        double support = 3.0;
        nHalfDots = (int) ((double) width * support / (double) scaleWidth);
        nDots = nHalfDots * 2 + 1;
        try {
            contrib = new double[nDots];
            normContrib = new double[nDots];
            tmpContrib = new double[nDots];
        } catch (Exception e) {
            System.out.println("init contrib,normContrib,tmpContrib" + e);
        }

        int center = nHalfDots;
        contrib[center] = 1.0;

        double weight = 0.0;
        int i;
        for (i = 1; i <= center; i++) {
            contrib[center + i] = Lanczos(i, width, scaleWidth, support);
            weight += contrib[center + i];
        }

        for (i = center - 1; i >= 0; i--) {
            contrib[i] = contrib[center * 2 - i];
        }

        weight = weight * 2 + 1.0;

        for (i = 0; i <= center; i++) {
            normContrib[i] = contrib[i] / weight;
        }

        for (i = center + 1; i < nDots; i++) {
            normContrib[i] = normContrib[center * 2 - i];
        }
    } // end of CalContrib()

    // 处理边缘
    private void CalTempContrib(int start, int stop) {
        double weight = 0;

        int i;
        for (i = start; i <= stop; i++) {
            weight += contrib[i];
        }

        for (i = start; i <= stop; i++) {
            tmpContrib[i] = contrib[i] / weight;
        }

    } // end of CalTempContrib()

    private int GetRedValue(int rgbValue) {
        int temp = rgbValue & 0x00ff0000;
        return temp >> 16;
    }

    private int GetGreenValue(int rgbValue) {
        int temp = rgbValue & 0x0000ff00;
        return temp >> 8;
    }

    private int GetBlueValue(int rgbValue) {
        return rgbValue & 0x000000ff;
    }

    private int ComRGB(int redValue, int greenValue, int blueValue) {

        return (redValue << 16) + (greenValue << 8) + blueValue;
    }

    public BufferedImage xFiltering(BufferedImage in, int size, boolean horizontal) {
        int width = in.getWidth();
        int height = in.getHeight();
        int value = horizontal ? width : height;
        int jStop = horizontal ? height : width;
        BufferedImage out = new BufferedImage(horizontal ? size : jStop, horizontal ? jStop : size, BufferedImage.TYPE_INT_RGB);

        for (int i = 0; i < size; i++) {

            int position = (int) (((double) i) * ((double) value) / ((double) size) + 0.5);
            int jStart;
            int fStart = position - nHalfDots;
            if (fStart < 0) {
                fStart = 0;
                jStart = nHalfDots - position;
            } else {
                jStart = 0;
            }

            int stop;
            int fStop = position + nHalfDots;
            if (fStop > (value - 1)) {
                fStop = value - 1;
                stop = nHalfDots + (value - 1 - position);
            } else {
                stop = nHalfDots * 2;
            }

            if (jStart > 0 || stop < nDots - 1) {
                CalTempContrib(jStart, stop);
                for (int j = 0; j < jStop; j++) {
                    out.setRGB(horizontal ? i : j, horizontal ? j : i, xFilter(in, fStart, fStop, jStart, j, horizontal, tmpContrib));
                }
            } else {
                for (int j = 0; j < jStop; j++) {
                    out.setRGB(horizontal ? i : j, horizontal ? j : i, xFilter(in, fStart, fStop, jStart, j, horizontal, normContrib));
                }
            }
        }
        return out;
    }

    // 图片水平滤波
    private BufferedImage HorizontalFiltering(BufferedImage in, int size) {
        return xFiltering(in, size, true);
    } // end of HorizontalFiltering()

    private BufferedImage VerticalFiltering(BufferedImage in, int size) {
        return xFiltering(in, size, false);
    } // end of VerticalFiltering()

    private int Clip(int x) {
        if (x < 0)
            return 0;
        return Math.min(x, 255);
    }

    private int xFilter(BufferedImage image, int fStart, int fStop, int jStart, Integer point, boolean horizontal, double[] pContrib) {
        double valueRed = 0.0;
        double valueGreen = 0.0;
        double valueBlue = 0.0;
        int valueRGB;
        int i, j;

        for (i = fStart, j = jStart; i <= fStop; i++, j++) {

            valueRGB = image.getRGB(horizontal ? i : point, horizontal ? point : i); //换方向

            valueRed += GetRedValue(valueRGB) * pContrib[j];
            valueGreen += GetGreenValue(valueRGB) * pContrib[j];
            valueBlue += GetBlueValue(valueRGB) * pContrib[j];
        }

        valueRGB = ComRGB(Clip((int) valueRed), Clip((int) valueGreen), Clip((int) valueBlue));
        return valueRGB;
    }

    /**
     * <dt>Description:imageZoomOut</dt>
     * <dd>对外的工具方法，直接输出压缩完的图片</dd>
     * <dd>author: wnfbx@163.com</dd>
     * <dd>version:1.0 <i>2022/1/21 23:24</i></dd>
     *
     * @param inputStream  图片输入流
     * @param outputStream 图片输出流
     * @param formatName   图片格式
     * @param w            压缩后的宽度
     * @param h            压缩后的高度
     * @since jdk1.7+
     */
    public static void imageZoomOut(InputStream inputStream, OutputStream outputStream, String formatName, int w, int h) {
        assert inputStream != null : "inputStream not null";
        assert outputStream != null : "outputStream not null";
        assert w > 0 : "宽度要大于0";
        assert h > 0 : "高度要大于0";
        log.debug("压缩宽高为{},{}", w, h);
        try {
            BufferedImage read = ImageIO.read(inputStream);
            log.debug("完成图片buffer读取");
            BufferedImage bufferedImage = new ImageScale().imageZoomOut(read, w, h, false);
            log.debug("完成图片压缩");
            ImageIO.write(bufferedImage, formatName, outputStream);
            log.debug("完成图片写出");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        imageZoomOut(null, null, null, -1, -1);
    }

}
